/** \file    read.cpp
  * \brief   Reading & parsing of xml stream.
  *	Don't worry if compilation of the file takes more than several minutes!
  * \date    2003
  * \author  Fedor Chelnokov
  * \version $Header: /home/cscom1r4/cvsroot/cs/xml/spirit/src/read.cpp,v 1.1 2004/03/22 09:24:09 cscom1r4 Exp $
  */

#include "xml/spirit.h"
#include "spirit.inl"
#include "charsets.h"

#include <fstream>
#include <boost/spirit.hpp>
#include <boost/spirit/core.hpp>
#include <boost/spirit/iterator/multi_pass.hpp>
#include <boost/spirit/tree/ast.hpp>

namespace xml {

class spirit_buider
{
	spirit & impl;
	spirit::element * node;
	string attr;
	string aval;
public:
	spirit_buider(spirit & i) : impl(i) {
		node = 0;
	}
	~spirit_buider() {
	}
	void enter_elem(const string & name) {
		assert(attr.empty());
		if (node == 0) {
			assert(impl._root == 0);
			impl.create(name);
			node = impl._root;
		}
		else
			node = reinterpret_cast<spirit::element*>(node->create_child(name));
	}
	void leave_elem(const string & name) {
		assert(node);
		assert(attr.empty());
		if (node->tag() != name)
			throw stream_parse_error();
		node = node->parent();
	}
	void leave_elem() {
		assert(node);
		assert(attr.empty());
		node = node->parent();
	}
	void set_attr(const string & name) {
		assert(node);
		assert(attr.empty());
		assert(aval.empty());
		attr = name;
	}
	void set_attr_part(const string & v) {
		assert(node);
		assert(!attr.empty());
		aval += v;
	}
	void finish_attr() {
		node->set_attribute(attr, aval);
		attr.clear();
		aval.clear();
	}
};

struct enter_elem_action
{
	spirit_buider & builder;
	enter_elem_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I first, I last) const 
		{ const_cast<spirit_buider &>(builder).enter_elem(string(first, last)); }
};

struct leave_elem_action
{
	spirit_buider & builder;
	leave_elem_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I first, I last) const 
		{ const_cast<spirit_buider &>(builder).leave_elem(string(first, last)); }
};

struct leave0_elem_action
{
	spirit_buider & builder;
	leave0_elem_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I, I) const 
		{ const_cast<spirit_buider &>(builder).leave_elem(); }
};

struct attr_name_action
{
	spirit_buider & builder;
	attr_name_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I first, I last) const
		{ const_cast<spirit_buider &>(builder).set_attr(string(first, last)); }
};

struct attr_part_action
{
	spirit_buider & builder;
	attr_part_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I first, I last) const 
		{ const_cast<spirit_buider &>(builder).set_attr_part(string(first, last)); }
};

struct attr_special_action
{
	spirit_buider & builder;
	attr_special_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I first, I last) const {
		string s(first, last);
		spirit_buider & b = const_cast<spirit_buider &>(builder);
		if (s == "&quot;")
			b.set_attr_part("\"");
		else if (s == "&amp;")
			b.set_attr_part("&");
		else if (s == "&lt;")
			b.set_attr_part("<");
		else if (s == "&apos;")
			b.set_attr_part("'");
		else
			b.set_attr_part(s);
	}
};

struct charcode_action
{
	spirit_buider & builder;
	charcode_action(spirit_buider & b) : builder(b) {}
	void operator () (unsigned c) const 
		{ const_cast<spirit_buider &>(builder).set_attr_part(string(1, char(c))); }
};

struct attr_finish_action
{
	spirit_buider & builder;
	attr_finish_action(spirit_buider & b) : builder(b) {}
	template <typename I>
	void operator () (I, I) const 
		{ const_cast<spirit_buider &>(builder).finish_attr(); }
};

using namespace std;
using namespace boost::spirit;

struct grammar : public boost::spirit::grammar<grammar>
{
	spirit_buider & builder;
	grammar(spirit_buider & b) : builder(b) {}

    template <typename ScannerT>
    struct definition
    {
        typedef typename std::iterator_traits<typename ScannerT::iterator_t>::value_type char_t;
        typedef boost::spirit::chset<wchar_t> chset_t;

        // non-ast generating rules
        typedef typename ScannerT::iteration_policy_t iteration_policy_t;
        typedef match_policy match_policy_t;
        typedef typename ScannerT::action_policy_t action_policy_t;
        typedef scanner_policies<
            iteration_policy_t,
            match_policy_t,
            action_policy_t
        > policies_t;
        typedef scanner<typename ScannerT::iterator_t, policies_t> non_tree_scanner_t;

        boost::spirit::rule<non_tree_scanner_t>
            PEReference2, Reference2, EntityRef2, CharRef2, Name2;

        boost::spirit::rule<ScannerT>
            prolog, element, Misc, PITarget, CData, Reference, EntityRef,
        doctypedecl, XMLDecl, SDDecl, VersionInfo, EncodingDecl, VersionNum,
        Eq, DeclSep, ExternalID, markupdecl, NotationDecl, EntityDecl,
        AttlistDecl, elementdecl, TextDecl, extSubsetDecl, conditionalSect,
        content, ETag, Attribute, contentspec, Mixed,
        children, choice, seq, cp, AttDef, AttType, DefaultDecl, StringType,
        TokenizedType, EnumeratedType, NotationType, Enumeration, EntityValue,
        AttValue, AttValueA, SystemLiteral, PubidLiteral, CharData, Comment,
        PI, CDSect, extSubset, includeSect, ignoreSect, ignoreSectContents,
        Ignore, GEDecl, PEDecl, EntityDef, PEDef, CharRef, PEReference,
        NDataDecl, extParsedEnt, EncName, PublicID, document, S, Name, Names,
        Nmtoken, Nmtokens;

        definition(grammar const & g)
        {

            document =
                prolog >> element >> *Misc;

// basically only gcc on linux has 4 byte wide chars
#if defined(__GNUC__) && defined(linux)
            chset_t Char(L"\x9\xA\xD\x20-\xD7FF\xE000-\xFFFD\x10000-\x10FFFF");
#else
            chset_t Char(L"\x9\xA\xD\x20-\xD7FF\xE000-\xFFFD");
#endif

            chset_t Sch(L"\x20\x9\xD\xA");
            S = 
                no_node_d[
                    +(Sch)
                ];

			const charsets & c = the_charsets;

            Name =
                no_node_d[
                    (c.Letter | L'_' | L':') >> *(c.NameChar)
                ];

            Name2 =
                (c.Letter | L'_' | L':') >> *(c.NameChar);

            Names =
                infix_node_d[
                    Name % S
                    //Name >> *(S >> Name)
                ];

            Nmtoken =
                no_node_d[
                    +c.NameChar
                ];

            Nmtokens =
                infix_node_d[
                    Nmtoken  % S
                    //Nmtoken >> *(S >> Nmtoken)
                ];

            EntityValue =
                no_node_d[
                      L'"' >> *(  (anychar_p - (chset_t(L"%&\""))) 
                                | PEReference2
                                | Reference2) 
                           >> L'"'
                    | L'\'' >> *(  (anychar_p - (chset_t(L"%&'"))) 
                                 | PEReference2
                                 | Reference2) 
                            >> L'\''
                ];

            AttValue = 
                no_node_d[
                      L'"' >> *(  (anychar_p - (chset_t(L"<&\""))) 
                                | Reference2) 
                           >> L'"'
                    | L'\'' >> *(  (anychar_p - (chset_t(L"<&'"))) 
                                 | Reference2) 
                            >> L'\''
                ];

			//fed
            AttValueA = 
                no_node_d[
                      L'"' >> *( Reference2 | 
								(+(anychar_p - chset_t(L"<&\"")))[attr_part_action(g.builder)])
                           >> L'"'
                    | L'\'' >> *( Reference2 | 
								(+(anychar_p - chset_t(L"<&'")))[attr_part_action(g.builder)])
                            >> L'\''
                ];

            SystemLiteral= 
                no_node_d[
                      (L'"' >> *(anychar_p - L'"') >> L'"')
                    | (L'\'' >> *(anychar_p - L'\'') >> L'\'')
                ];

            chset_t PubidChar(L"\x20\xD\xA'a-zA-Z0-9()+,./:=?;!*#@$_%-");

            PubidLiteral = 
                no_node_d[
                      L'"' >> *PubidChar >> L'"' 
                    | L'\'' >> *(PubidChar - L'\'') >> L'\''
                ];

            chset_t CharDataChar = 
                anychar_p - (chset_t(L"<&"));

            CharData =
                no_node_d[
                    *(CharDataChar - L"]]>")
                ];

            Comment = 
                no_node_d[
                    L"<!--" >> 
                      *(
                          (Char - L'-') 
                        | (L'-' >> (Char - L'-'))
                       ) 
                      >> L"-->"
                ];

            PI = 
                token_node_d[
                    L"<?" >> PITarget >> !(S >> (*(Char - L"?>"))) >> L"?>"
                ];

            PITarget =
                Name - (as_lower_d[L"xml"]);

            CDSect =
                L"<![CDATA[" >> CData >> L"]]>";

            CData =
                no_node_d[
                    *(Char - L"]]>")
                ];

            prolog =
                !XMLDecl >> *Misc >> !(doctypedecl >> *Misc);

            XMLDecl =
                L"<?xml" >> VersionInfo >> !EncodingDecl >> !SDDecl 
                >> !S >> L"?>";

            VersionInfo = 
                S >> L"version" >> Eq >> 
                (
                   L'\'' >> VersionNum >> L'\''
                 | L'"' >> VersionNum >> L'"'
                );

            Eq =
                !S >> L'=' >> !S;

            chset_t VersionNumCh(L"a-zA-Z0-9_.:-");

            VersionNum =
                no_node_d[
                    +(VersionNumCh)
                ];

            Misc =
                  Comment 
                | PI 
                | S;

            doctypedecl =
                L"<!DOCTYPE" >> S >> Name >> !(S >> ExternalID) >> !S >> 
                !(
                  L'[' >> *(markupdecl | DeclSep) >> L']' >> !S
                ) 
                >> L'>';

            DeclSep =
                  PEReference
                | S;

            markupdecl =
                  elementdecl 
                | AttlistDecl 
                | EntityDecl 
                | NotationDecl 
                | PI 
                | Comment;

            extSubset =
                !TextDecl >> extSubsetDecl;

            extSubsetDecl =
                *(
                    markupdecl 
                  | conditionalSect 
                  | DeclSep
                );

            SDDecl = 
                S >> L"standalone" >> Eq >> 
                (
                   (L'\'' >> (str_p(L"yes") | L"no") >> L'\'')
                 | (L'"' >> (str_p(L"yes") | L"no") >> L'"')
                );

            element =
				//fed
				L'<' >> Name[enter_elem_action(g.builder)] >> *(S >> Attribute) >> !S >>
					(str_p(L"/>")[leave0_elem_action(g.builder)] 
					| ch_p(L'>') >> content >> ETag);

            Attribute =
                Name[attr_name_action(g.builder)] >> Eq >> 
				AttValueA[attr_finish_action(g.builder)];

            ETag =
                L"</" >> Name[leave_elem_action(g.builder)] >> !S >> L'>';

            content =
                !CharData >> 
                *(
                  (
                     element 
                   | Reference
                   | CDSect 
                   | PI 
                   | Comment
                  ) >> !CharData
                 );

            elementdecl = 
                L"<!ELEMENT" >> S >> Name >> S >> contentspec >> !S >> L'>';

            contentspec = 
                  str_p(L"EMPTY") 
                | L"ANY" 
                | Mixed 
                | children;

            children =
                (choice | seq) >> !(ch_p(L'?') | L'*' | L'+');

            cp = 
                (Name | choice | seq) >> !(ch_p(L'?') | L'*' | L'+');

            choice = 
                L'(' >> !S >> cp 
                  >> +(!S >> L'|' >> !S >> cp) 
                  >> !S >> L')';

            seq =
                L'(' >> !S >> cp >> 
                  *(!S >> L',' >> !S >> cp) 
                  >> !S >> L')';

            Mixed =
                  L'(' >> !S >> L"#PCDATA" 
                      >> *(!S >> L'|' >> !S >> Name) 
                      >> !S >> L")*"
                | L'(' >> !S >> L"#PCDATA" >> !S >> L')';

            AttlistDecl =
                L"<!ATTLIST" >> S >> Name >> *AttDef >> !S >> L'>';

            AttDef =
                S >> Name >> S >> AttType >> S >> DefaultDecl;

            AttType =
                  StringType 
                | TokenizedType 
                | EnumeratedType;

            StringType =
                str_p(L"CDATA");

            TokenizedType =
                longest_d[ 
                      str_p(L"ID") 
                    | L"IDREF" 
                    | L"IDREFS" 
                    | L"ENTITY" 
                    | L"ENTITIES" 
                    | L"NMTOKEN"
                    | L"NMTOKENS" 
                ];

            EnumeratedType =
                  NotationType 
                | Enumeration;

            NotationType =
                L"NOTATION" >> S >> L'(' >> !S >> Name 
                  >> *(!S >> L'|' >> !S >> Name) 
                  >> !S >> L')';

            Enumeration = 
                L'(' >> !S >> Nmtoken 
                >> *(!S >> L'|' >> !S >> Nmtoken) 
                >> !S >> L')';

            DefaultDecl =
                  str_p(L"#REQUIRED") 
                | L"#IMPLIED" 
                | !(L"#FIXED" >> S) >> AttValue;

            conditionalSect =
                  includeSect 
                | ignoreSect;

            includeSect =
                L"<![" >> !S >> L"INCLUDE" >> !S 
                >> L'[' >> extSubsetDecl >> L"]]>";

            ignoreSect = 
                L"<![" >> !S >> L"IGNORE"  >> !S 
                >> L'[' >> *ignoreSectContents >> L"]]>";

            ignoreSectContents = 
                Ignore >> *(L"<![" >> ignoreSectContents >> L"]]>" >> Ignore);

            Ignore = 
                *(Char - (str_p(L"<![") | L"]]>"));

            CharRef = 
                token_node_d[
                      L"&#"  >> +digit_p  >> L';'
                    | L"&#x" >> +xdigit_p >> L';'
                ];

            CharRef2 = 
                  L"&#"  >> uint_p[charcode_action(g.builder)]   >> L';'
                | L"&#x" >> hex_p[charcode_action(g.builder)] >> L';';

            Reference =
                  EntityRef
                | CharRef;

            Reference2 =
                  EntityRef2
                | CharRef2;

            EntityRef =
                token_node_d[
                    L'&' >> Name >> L';'
                ];

            EntityRef2 =
                (L'&' >> Name2 >> L';')[attr_special_action(g.builder)];

            PEReference =
                token_node_d[
                    L'%' >> Name >> L';'
                ];

            PEReference2 =
                L'%' >> Name2 >> L';';

            EntityDecl =
                  GEDecl 
                | PEDecl;

            GEDecl =
                L"<!ENTITY" >> S >> Name >> S >> EntityDef >> !S >> L'>';

            PEDecl =
                L"<!ENTITY" >> S >> L'%' >> S >> Name >> S >> PEDef 
                >> !S >> L'>';

            EntityDef =
                  EntityValue
                | ExternalID >> !NDataDecl;

            PEDef =
                  EntityValue 
                | ExternalID;

            ExternalID =
                  L"SYSTEM" >> S >> SystemLiteral
                | L"PUBLIC" >> S >> PubidLiteral >> S >> SystemLiteral;

            NDataDecl =
                S >> L"NDATA" >> S >> Name;

            TextDecl =
                L"<?xml" >> !VersionInfo >> EncodingDecl >> !S >> L"?>";

            extParsedEnt =
                !TextDecl >> content;

            EncodingDecl =
                S >> L"encoding" >> Eq 
                >> (  L'"' >> EncName >> L'"' 
                    | L'\'' >> EncName >> L'\''
                   );

            EncName =
                no_node_d[
                    alpha_p >> *(alnum_p | L'.' | L'_' | L'-')
                ];

            NotationDecl =
                L"<!NOTATION" >> S >> Name >> S 
                >> (ExternalID | PublicID) >> !S >> L'>';

            PublicID =
                L"PUBLIC" >> S >> PubidLiteral;


            BOOST_SPIRIT_DEBUG_RULE(document);
            BOOST_SPIRIT_DEBUG_RULE(prolog);
            BOOST_SPIRIT_DEBUG_RULE(element);
            BOOST_SPIRIT_DEBUG_RULE(Misc);
            BOOST_SPIRIT_DEBUG_RULE(PEReference);
            BOOST_SPIRIT_DEBUG_RULE(PEReference2);
            BOOST_SPIRIT_DEBUG_RULE(Reference);
            BOOST_SPIRIT_DEBUG_RULE(Reference2);
            BOOST_SPIRIT_DEBUG_RULE(PITarget);
            BOOST_SPIRIT_DEBUG_RULE(CData);
            BOOST_SPIRIT_DEBUG_RULE(doctypedecl);
            BOOST_SPIRIT_DEBUG_RULE(XMLDecl);
            BOOST_SPIRIT_DEBUG_RULE(SDDecl);
            BOOST_SPIRIT_DEBUG_RULE(VersionInfo);
            BOOST_SPIRIT_DEBUG_RULE(EncodingDecl);
            BOOST_SPIRIT_DEBUG_RULE(VersionNum);
            BOOST_SPIRIT_DEBUG_RULE(Eq);
            BOOST_SPIRIT_DEBUG_RULE(DeclSep);
            BOOST_SPIRIT_DEBUG_RULE(ExternalID);
            BOOST_SPIRIT_DEBUG_RULE(markupdecl);
            BOOST_SPIRIT_DEBUG_RULE(NotationDecl);
            BOOST_SPIRIT_DEBUG_RULE(EntityDecl);
            BOOST_SPIRIT_DEBUG_RULE(AttlistDecl);
            BOOST_SPIRIT_DEBUG_RULE(elementdecl);
            BOOST_SPIRIT_DEBUG_RULE(TextDecl);
            BOOST_SPIRIT_DEBUG_RULE(extSubsetDecl);
            BOOST_SPIRIT_DEBUG_RULE(conditionalSect);
            BOOST_SPIRIT_DEBUG_RULE(EmptyElemTag);
            BOOST_SPIRIT_DEBUG_RULE(content);
            BOOST_SPIRIT_DEBUG_RULE(ETag);
            BOOST_SPIRIT_DEBUG_RULE(Attribute);
            BOOST_SPIRIT_DEBUG_RULE(contentspec);
            BOOST_SPIRIT_DEBUG_RULE(Mixed);
            BOOST_SPIRIT_DEBUG_RULE(children);
            BOOST_SPIRIT_DEBUG_RULE(choice);
            BOOST_SPIRIT_DEBUG_RULE(seq);
            BOOST_SPIRIT_DEBUG_RULE(cp);
            BOOST_SPIRIT_DEBUG_RULE(AttDef);
            BOOST_SPIRIT_DEBUG_RULE(AttType);
            BOOST_SPIRIT_DEBUG_RULE(DefaultDecl);
            BOOST_SPIRIT_DEBUG_RULE(StringType);
            BOOST_SPIRIT_DEBUG_RULE(TokenizedType);
            BOOST_SPIRIT_DEBUG_RULE(EnumeratedType);
            BOOST_SPIRIT_DEBUG_RULE(NotationType);
            BOOST_SPIRIT_DEBUG_RULE(Enumeration);
            BOOST_SPIRIT_DEBUG_RULE(EntityValue);
            BOOST_SPIRIT_DEBUG_RULE(AttValue);
            BOOST_SPIRIT_DEBUG_RULE(AttValueA);
            BOOST_SPIRIT_DEBUG_RULE(SystemLiteral);
            BOOST_SPIRIT_DEBUG_RULE(PubidLiteral);
            BOOST_SPIRIT_DEBUG_RULE(CharData);
            BOOST_SPIRIT_DEBUG_RULE(Comment);
            BOOST_SPIRIT_DEBUG_RULE(PI);
            BOOST_SPIRIT_DEBUG_RULE(CDSect);
            BOOST_SPIRIT_DEBUG_RULE(extSubset);
            BOOST_SPIRIT_DEBUG_RULE(includeSect);
            BOOST_SPIRIT_DEBUG_RULE(ignoreSect);
            BOOST_SPIRIT_DEBUG_RULE(ignoreSectContents);
            BOOST_SPIRIT_DEBUG_RULE(Ignore);
            BOOST_SPIRIT_DEBUG_RULE(CharRef);
            BOOST_SPIRIT_DEBUG_RULE(CharRef2);
            BOOST_SPIRIT_DEBUG_RULE(EntityRef);
            BOOST_SPIRIT_DEBUG_RULE(EntityRef2);
            BOOST_SPIRIT_DEBUG_RULE(GEDecl);
            BOOST_SPIRIT_DEBUG_RULE(PEDecl);
            BOOST_SPIRIT_DEBUG_RULE(EntityDef);
            BOOST_SPIRIT_DEBUG_RULE(PEDef);
            BOOST_SPIRIT_DEBUG_RULE(NDataDecl);
            BOOST_SPIRIT_DEBUG_RULE(extParsedEnt);
            BOOST_SPIRIT_DEBUG_RULE(EncName);
            BOOST_SPIRIT_DEBUG_RULE(PublicID);
            BOOST_SPIRIT_DEBUG_RULE(document);
            BOOST_SPIRIT_DEBUG_RULE(S);
            BOOST_SPIRIT_DEBUG_RULE(Name);
            BOOST_SPIRIT_DEBUG_RULE(Names);
            BOOST_SPIRIT_DEBUG_RULE(Nmtoken);
            BOOST_SPIRIT_DEBUG_RULE(Nmtokens);

        }

        boost::spirit::rule<ScannerT> const&
        start() const
        {
            return document;
        }

    };
};

void spirit::parse(const string & xmlstream)
{
	clear();
	turn_off_heap_dbg x;
	spirit_buider builder(*this);
    grammar g(builder);
    if (!boost::spirit::parse(xmlstream.begin(), xmlstream.end(), g).full)
		throw stream_parse_error();
}

void spirit::read(const string & filename)
{
    ifstream in;
	in.exceptions(std::ios::badbit | std::ios::failbit);
	in.open(filename.c_str(), std::ios::binary);

	//typedef char char_t;
	//typedef multi_pass<istreambuf_iterator<char_t> > iterator_t;
	//iterator_t first = make_multi_pass(istreambuf_iterator<char_t>(in.rdbuf()));
	//iterator_t last = make_multi_pass(istreambuf_iterator<char_t>());
	//parse_info<iterator_t> pi = parse(first, last, rule);

	parse(
		string(
            istreambuf_iterator<char>(in.rdbuf()),
            istreambuf_iterator<char>()));
}

bool valid_name(const string & name)
{
	const charsets & c = the_charsets;
	return parse(name.begin(), name.end(), 
		(c.Letter | L'_' | L':') >> *(c.NameChar)).full;
}

} //namespace xml
